﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using XcoAppSpaces.Core;
using System.Threading;
using XcoAppSpaces.Core.Ports;
using XcoAppSpaces.Contracts.Security;
using XcoAppSpaces.Core.Communication;
using XcoAppSpaces.Core.Configuration;
using XcoAppSpaces.Security.Basic;
using XcoAppSpaces.Transport.Sockets;
using XcoAppSpaces.Contracts.Exceptions;
using System.IO;
using System.Reflection;
using System.Net;

namespace test.XcoAppSpaces.Core
{
	#region Workers
	public class SecureWorkerContract : PortSet<int, string, Subscribe<string>, Unsubscribe<string>>
	{
	}

	[XcoSecurityPermission("WorkerRole")]
	class MySecureWorker : SecureWorkerContract
	{
		public int intMsg;
		public string strMsg;

		[XcoSecurityPermission("StrPortRole")] //users with StrPortRole should be able to use the publisher
		XcoPublisher<string> publisher = new XcoPublisher<string>();

		[XcoExclusive]
		[XcoSecurityPermission("IntPortRole")]
		public void ProcessInt(int msg)
		{
			this.intMsg = msg;
		}

		[XcoConcurrent]
		[XcoSecurityPermission("StrPortRole")]
		public void ProcessString(string msg)
		{
			this.strMsg = msg;
			publisher.Publish(msg);
		}
	}

	public class SecureWorkerContract2 : PortSet<int, string, Subscribe<string>, Unsubscribe<string>>
	{
	}

	[XcoSecurityPermission("WorkerRole")]
	class MySecureWorker2 : SecureWorkerContract2
	{
	}

	public class UnsecureWorkerContract : PortSet<int, string>
	{
	}

	class MyUnsecureWorker : UnsecureWorkerContract
	{
		public int intMsg;
		public string strMsg;

		[XcoExclusive]
		public void ProcessInt(int msg)
		{
			this.intMsg = msg;
		}

		[XcoConcurrent]
		[XcoSecurityPermission("StrPortRole")]
		public void ProcessString(string msg)
		{
			this.strMsg = msg;
		}
	}
	#endregion

	[TestFixture]
	public class testSecurity
	{
		private int port1;
		private int port2;
		private string spaceAddress1;
		private string spaceAddress2;
		//private string testWorkerName;

		[SetUp]
		public void Setup()
		{
			port1 = 8001;
			port2 = 8002;
			spaceAddress1 = Environment.MachineName + ":" + port1;
			spaceAddress2 = Environment.MachineName + ":" + port2;
			//testWorkerName = "testworker";
		}

		[TearDown]
		public void Teardown()
		{
		}

		[Test]
		public void TestStringConfig()
		{
			using (var space = new XcoAppSpace("security.policy=restrictive;security.store=test.xml"))
			{
				Assert.IsNotNull(space.Info.SecurityService);
				Assert.IsInstanceOf(typeof(XcoBasicSecurityService), space.Info.SecurityService);
				var secService = (XcoBasicSecurityService) space.Info.SecurityService;
				Assert.AreEqual(XcoSecurityPolicy.Restrictive, secService.Policy);
				Assert.AreEqual("test.xml", secService.StorePath);
			}
		}

		[Test]
		public void TestFluentConfig()
		{
			var secService = new XcoBasicSecurityService();
			using (var space = XcoAppSpace.Configure.UsingService(secService).WithPolicy(XcoSecurityPolicy.Restrictive).WithStorePath("test.xml").Create())
			{
				Assert.IsNotNull(space.Info.SecurityService);
				Assert.AreEqual(secService, space.Info.SecurityService);
				Assert.AreEqual(XcoSecurityPolicy.Restrictive, secService.Policy);
				Assert.AreEqual("test.xml", secService.StorePath);
			}
		}

		[Test]
		public void TestSecurityWorkerRole()
		{
			XcoBasicSecurityService security = new XcoBasicSecurityService();
			security.AddUser("test", "123", "WorkerRole");
			using (XcoAppSpace space1 = XcoAppSpace.Configure.UsingService(new XcoTCPTransportService()).OnPort(port1).UsingService(security),
				space2 = new XcoAppSpace("tcp.port=" + port2))
			{
				//run worker
				space1.RunWorker<SecureWorkerContract, MySecureWorker>();
				MySecureWorker localWorker = (MySecureWorker)space1.Resolve<SecureWorkerContract>();

				//remote worker over tcp
				space2.Credentials = new NetworkCredential("test", "123");
				SecureWorkerContract remoteWorkerTcp = space2.ConnectWorker<SecureWorkerContract>(spaceAddress1);
				//tests for remote worker access and remote port
				remoteWorkerTcp.Post(1);
				remoteWorkerTcp.Post("testTcp");
				Thread.Sleep(300);
				Assert.AreEqual(1, localWorker.intMsg);
				Assert.AreEqual("testTcp", localWorker.strMsg);
			}
		}

		[Test]
		public void TestSecurityWorkerRole_MultipleWorkers()
		{
			XcoBasicSecurityService security = new XcoBasicSecurityService();
			security.AddUser("test", "123", "WorkerRole");
			using (XcoAppSpace space1 = XcoAppSpace.Configure.UsingService<XcoTCPTransportService>().OnPort(port1).UsingService(security),
				space2 = new XcoAppSpace("tcp.port=" + port2))
			{
				//run worker
				space1.RunWorker<SecureWorkerContract, MySecureWorker>("test");
				space1.RunWorker<SecureWorkerContract2, MySecureWorker2>("test");
				
				
				
			}
		}

		[Test]
		public void TestSecurityPortRoles()
		{
			XcoBasicSecurityService security = new XcoBasicSecurityService();
			security.AddUser("test", "123", "IntPortRole", "StrPortRole");
			using (XcoAppSpace space1 = XcoAppSpace.Configure.UsingService(new XcoTCPTransportService()).OnPort(port1).UsingService(security), 
				space2 = new XcoAppSpace("tcp.port=" + port2))
			{
				//run worker
				space1.RunWorker<SecureWorkerContract, MySecureWorker>();
				MySecureWorker localWorker = (MySecureWorker)space1.Resolve<SecureWorkerContract>();

				//remote worker over tcp
				space2.Credentials = new NetworkCredential("test", "123");
				SecureWorkerContract remoteWorkerTcp = space2.ConnectWorker<SecureWorkerContract>(spaceAddress1);
				//tests for remote worker access and remote port
				remoteWorkerTcp.Post(1);
				remoteWorkerTcp.Post("testTcp");
				Thread.Sleep(300);
				Assert.AreEqual(1, localWorker.intMsg);
				Assert.AreEqual("testTcp", localWorker.strMsg);
			}
		}

		[Test]
		public void TestSecurityXcoPublisher()
		{
			XcoBasicSecurityService security = new XcoBasicSecurityService();
			security.AddUser("test", "123", "StrPortRole"); //StrPortRole should have the right to post messages to XcoPublisher
			using (XcoAppSpace space1 = XcoAppSpace.Configure.UsingService(new XcoTCPTransportService()).OnPort(port1).UsingService(security),
				space2 = new XcoAppSpace("tcp.port=" + port2))
			{
				//run worker
				space1.RunWorker<SecureWorkerContract, MySecureWorker>();
				MySecureWorker localWorker = (MySecureWorker)space1.Resolve<SecureWorkerContract>();

				//remote worker over tcp
				space2.Credentials = new NetworkCredential("test", "123");
				SecureWorkerContract remoteWorkerTcp = space2.ConnectWorker<SecureWorkerContract>(spaceAddress1);

				//tests for remote worker access and remote port
				var subscriptionPort = new Port<string>();
				remoteWorkerTcp.Post(new Subscribe<string>(subscriptionPort));
				Thread.Sleep(100);
				remoteWorkerTcp.Post("testTcp");
				var result = subscriptionPort.Receive(2000).Result;
				
				Assert.AreEqual("testTcp", result);
			}
		}

		[Test]
		[ExpectedException(typeof(XcoSecurityException))]
		public void TestSecurityWrongPass()
		{
			XcoBasicSecurityService security = new XcoBasicSecurityService();
			security.AddUser("test", "123", "MyRole", "MyRole2");
			using (XcoAppSpace space1 = XcoAppSpace.Configure.UsingService(new XcoTCPTransportService()).OnPort(port1).UsingService(security),
				space2 = new XcoAppSpace("tcp.port=" + port2))
			{
				//run worker
				space1.RunWorker<SecureWorkerContract, MySecureWorker>();
				MySecureWorker localWorker = (MySecureWorker)space1.Resolve<SecureWorkerContract>();

				//remote worker over tcp
				space2.Credentials = new NetworkCredential("test", "123x");
				SecureWorkerContract remoteWorkerTcp = space2.ConnectWorker<SecureWorkerContract>(spaceAddress1);
			}
		}

		[Test]
		[ExpectedException(typeof(XcoSecurityException))]
		public void TestSecurityNoRoles()
		{
			XcoBasicSecurityService security = new XcoBasicSecurityService();
			security.AddUser("test", "123");
			using (XcoAppSpace space1 = XcoAppSpace.Configure.UsingService(new XcoTCPTransportService()).OnPort(port1).UsingService(security),
				space2 = new XcoAppSpace("tcp.port=" + port2))
			{
				//run worker
				space1.RunWorker<SecureWorkerContract, MySecureWorker>();
				MySecureWorker localWorker = (MySecureWorker)space1.Resolve<SecureWorkerContract>();

				//connecting to worker should not be possible because of missing roles
				space2.Credentials = new NetworkCredential("test", "123");
				SecureWorkerContract remoteWorkerTcp = space2.ConnectWorker<SecureWorkerContract>(spaceAddress1);
			}
		}

		[Test]
		public void TestSecurityPartialAccess()
		{
			XcoBasicSecurityService security = new XcoBasicSecurityService();
			security.AddUser("test", "123", "IntPortRole");
			using (XcoAppSpace space1 = XcoAppSpace.Configure.UsingService(new XcoTCPTransportService()).OnPort(port1).UsingService(security),
				space2 = new XcoAppSpace("tcp.port=" + port2))
			{
				//run worker
				space1.RunWorker<SecureWorkerContract, MySecureWorker>();
				MySecureWorker localWorker = (MySecureWorker)space1.Resolve<SecureWorkerContract>();

				//remote worker over tcp
				space2.Credentials = new NetworkCredential("test", "123");
				SecureWorkerContract remoteWorkerTcp = space2.ConnectWorker<SecureWorkerContract>(spaceAddress1);
				
				//this port should be accessible
				Port<Exception> exceptionPort = new Port<Exception>();
				remoteWorkerTcp.P0.Post(1, exceptionPort);
				Thread.Sleep(300);
				Assert.AreEqual(1, localWorker.intMsg);

				//this port should throw a security exception
				remoteWorkerTcp.P1.Post("testTcp", exceptionPort);
				Exception ex = exceptionPort.Receive(1000).Result;
				Assert.IsAssignableFrom(typeof(XcoSecurityException), ex);
			}
		}

		[Test]
		public void TestUnsecureWorker()
		{
			XcoBasicSecurityService security = new XcoBasicSecurityService();
			security.AddUser("test", "123", "IntPortRole", "StrPortRole");
			using (XcoAppSpace space1 = XcoAppSpace.Configure.UsingService(new XcoTCPTransportService()).OnPort(port1).UsingService(security),
				space2 = new XcoAppSpace("tcp.port=" + port2))
			{
				//run worker
				space1.RunWorker<UnsecureWorkerContract, MyUnsecureWorker>();
				MyUnsecureWorker localWorker = (MyUnsecureWorker)space1.Resolve<UnsecureWorkerContract>();

				//connect without credentials
				UnsecureWorkerContract remoteWorkerTcp = space2.ConnectWorker<UnsecureWorkerContract>(spaceAddress1);

				//this port should be accessible
				Port<Exception> exceptionPort = new Port<Exception>();
				remoteWorkerTcp.P0.Post(1, exceptionPort);
				Thread.Sleep(300);
				Assert.AreEqual(1, localWorker.intMsg);

				//this port should throw a security exception
				remoteWorkerTcp.P1.Post("testTcp", exceptionPort);
				Exception ex = exceptionPort.Receive(1000).Result;
				Assert.IsAssignableFrom(typeof(XcoSecurityException), ex);
			}
		}

		[Test]
		public void TestUnsecureWorker2()
		{
			XcoBasicSecurityService security = new XcoBasicSecurityService();
			security.AddUser("test", "123", "IntPortRole", "StrPortRole");
			using (XcoAppSpace space1 = XcoAppSpace.Configure.UsingService(new XcoTCPTransportService()).OnPort(port1).UsingService(security),
				space2 = new XcoAppSpace("tcp.port=" + port2))
			{
				//run worker
				space1.RunWorker<UnsecureWorkerContract, MyUnsecureWorker>();
				MyUnsecureWorker localWorker = (MyUnsecureWorker)space1.Resolve<UnsecureWorkerContract>();

				//connect without credentials
				space2.Credentials = new NetworkCredential("test", "123");
				UnsecureWorkerContract remoteWorkerTcp = space2.ConnectWorker<UnsecureWorkerContract>(spaceAddress1);

				//tests for remote worker access and remote port
				remoteWorkerTcp.Post(1);
				remoteWorkerTcp.Post("testTcp");
				Thread.Sleep(300);
				Assert.AreEqual(1, localWorker.intMsg);
				Assert.AreEqual("testTcp", localWorker.strMsg);
			}
		}

		[Test]
		public void TestUserStore()
		{
			//create security service and save users to file
			XcoBasicSecurityService security = new XcoBasicSecurityService();
			security.AddUser("test", "123", "IntPortRole", "StrPortRole");
			security.SaveUsers();
			
			//should now automatically load users from store
			security = new XcoBasicSecurityService(null);
			using (XcoAppSpace space1 = XcoAppSpace.Configure.UsingService(new XcoTCPTransportService()).OnPort(port1).UsingService(security),
				space2 = new XcoAppSpace("tcp.port=" + port2))
			{
				//run worker
				space1.RunWorker<UnsecureWorkerContract, MyUnsecureWorker>();
				MyUnsecureWorker localWorker = (MyUnsecureWorker)space1.Resolve<UnsecureWorkerContract>();

				//connect without credentials
				space2.Credentials = new NetworkCredential("test", "123");
				UnsecureWorkerContract remoteWorkerTcp = space2.ConnectWorker<UnsecureWorkerContract>(spaceAddress1);

				//tests for remote worker access and remote port
				remoteWorkerTcp.Post(1);
				remoteWorkerTcp.Post("testTcp");
				Thread.Sleep(300);
				Assert.AreEqual(1, localWorker.intMsg);
				Assert.AreEqual("testTcp", localWorker.strMsg);
			}
		}

		[Test]
		public void TestUserStorePath()
		{
			//create security service and save users to file
			string path = Path.Combine(Path.GetDirectoryName(Assembly.GetAssembly(typeof(XcoAppSpace)).Location), "users.xml");
			if (File.Exists(path))
				File.Delete(path);
			XcoBasicSecurityService security = new XcoBasicSecurityService(path);
			security.AddUser("test", "123", "IntPortRole", "StrPortRole");
			security.SaveUsers();

			Assert.IsTrue(File.Exists(path));
		}

		[Test]
		public void TestUserStorePath2()
		{
			//create security service and save users to file
			string path = Path.Combine(Path.GetDirectoryName(Assembly.GetAssembly(typeof(XcoAppSpace)).Location), "users.xml");
			if (File.Exists(path))
				File.Delete(path);
			XcoBasicSecurityService security = new XcoBasicSecurityService();
			security.StorePath = path;
			security.AddUser("test", "123", "IntPortRole", "StrPortRole");
			security.SaveUsers();

			Assert.IsTrue(File.Exists(path));
		}

		[Test]
		public void TestConfigString()
		{
			string path = Path.Combine(Path.GetDirectoryName(Assembly.GetAssembly(typeof(XcoAppSpace)).Location), "users.xml");
			if (File.Exists(path))
				File.Delete(path);
			
			//create security service and save users to file
			XcoBasicSecurityService security = new XcoBasicSecurityService(path);
			security.AddUser("test", "123", "IntPortRole", "StrPortRole");
			security.SaveUsers();

			//should now automatically load users from store
			security = new XcoBasicSecurityService(null);
			using (XcoAppSpace space = new XcoAppSpace("tcp.port=" + port1 + ";security.policy=restrictive;security.store=" + path))
			{
				Assert.IsInstanceOf(typeof(XcoBasicSecurityService), space.Info.SecurityService);
				XcoBasicSecurityService secService = (XcoBasicSecurityService)space.Info.SecurityService;
				Assert.AreEqual(XcoSecurityPolicy.Restrictive, secService.Policy);
				Assert.AreEqual(path, secService.StorePath);
			}
		}
	}
}
